| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 | 1
1
30
30
47
47
6
41
30
6
2
6
2
4
30
24
10
24
30
5
30
5
5
5
1
5
4
4
1
30
9
1
1
8
8
8
7
7
1
1
8
30
5
5
1
1
30
| (function() {
'use strict';
/**
* @ngdoc service
* @name featuresService
* @description
* The featuresService is the main point of interaction between the `oblique-features` module and AngularJS
* code. This service proivdes the following interaction capabilities:
*
* 1. Checking feature enabled / disabled status; and
* 2. Toggling individual features; and
* 3. Setting data dictionaries of enabled / disabled features from:
* 1. Hard-coded JSON sources; or
* 2. Remote JSON sources a single time or on a repeating schedule.
*
* ## Checking Feature Status
*
* In order to determine if a feature should be enabled or disabled, the service provides the
* `checkFeatureEnabled(featureName)`. This function will return true if the feature is either explicitly
* enabled or is omitted from the data set or false if the feature is explicitly disabled.
*
* ### Base Required For All Other Features?
*
* If desired, it is possible for the enabled / disabled status of individual features to be influenced by whether
* or not the base feature is enabled. If, for example, a security flaw is found, it would be possible to disable
* every feature by simply setting `base` to be false, and calling
* `featuresService.setBaseRequiredForAllFeatures(true)`.
*
* At this point, if `base` ever becomes disabled, any element with an `of-feature` element will be disabled.
*
* ## Disabling Features
* For ease of use and mitigation of unintended consequences, all features are enabled by default. In order
* to prevent a feature from being displayed, the feature must be disabled. There are multiple ways of disabling
* features in `oblique-features`:
*
* 1. Toggling the individual feature; or
* 2. Setting a feature dictionary with the feature disabled; or
* 3. Setting a remote JSON file with the desired features disabled in it.
*
* ### Toggling Individual Features
* This is more useful for testing applications, to ensure that when a feature is disabled, all of the
* relevant items are hidden and that it does not have any adverse effect on the remainder of the web application.
*
* To toggle an individual feature, one could set up the AngularJS JavaScript as follows:
<example>
angular.module('some-module').controller('someController', ['featuresService', function(featuresService) {
this.toggleFeature = function(featureName) {
featuresService.toggleFeature(featureName);
}
}]);
</example>
* And then have an HTML setup as follows:
```html
<div class="someFeatureDiv" of-feature feature="disableMe">
<!-- Some content goes here -->
</div>
<button ng-click="ctrl.toggleFeature('disableMe')">Disable Feature</button>
```
* ### Disabling Features Via Hard-Coded JSON Data
* This functionality is most useful if it is unlikely that deployed functionality will need to be adjusted on the
* fly. In this method of feature disabling, the hard-coded JSON source is passed into the service and will
* over-write any other feature enabled / disabled status which had been previously set. As an example:
*
<example>
angular.module('some-module').controller('someController', ['featuresService', function(featuresService) {
var jsonData = {
base: true,
signup: false,
accountDeletion: false,
dataViewing: true
};
featuresService.setHardCodedSource(jsonData);
}]);
</example>
*
* In this example, signup and accountDeletion functionality would be disabled while all other functionality would
* be enabled.
*
* ### Disabling Features Via Remote JSON Data
* This functionality is most useful if there is a high probability that functionality will need to be enabled /
* disabled on the fly without a full re-deploy of the web application. In this case, the functionality dictionary
* needs to be deployed to a known URI which can be passed in to the setRemoteSource method.
*
* With the remote source, it is also possible to set a timeout to retrieve the remote source again to facilitate
* hot-disabling of functionality within a certain timeframe. Taking the JSON data from the above example, if it were
* to be placed at http://foofoo.net/features.json, could be retrieved every 5 minutes by doing the following:
*
<example>
angular.module('some-module').controller('someController', ['featuresService', function(featuresService) {
featuresService.setRemoteSource('http://foofoo.net/features.json', 5*60*1000);
}]);
</example>
*
*
*/
angular.module('oblique-features')
.factory('featuresService',
['$log', '$http', '$interval', '$rootScope',
function($log, $http, $interval, $rootScope) {
var FeaturesService = {
remoteSource: null,
intervalId: null,
baseRequiredForAllFeatures: false,
featuresEnabledStatus: {
base: true
}
};
FeaturesService.checkFeatureEnabled = function(featureName) {
featureName = featureName ? featureName : 'base';
if (FeaturesService.baseRequiredForAllFeatures) {
return (FeaturesService.featuresEnabledStatus.base &&
(FeaturesService.featuresEnabledStatus[featureName] === true ||
FeaturesService.featuresEnabledStatus[featureName] === undefined));
} else {
return (FeaturesService.featuresEnabledStatus[featureName] ||
FeaturesService.featuresEnabledStatus[featureName] === undefined);
}
};
FeaturesService.toggleFeature = function(featureName) {
if (!featureName) {
featureName = 'base';
}
if (undefined === FeaturesService.featuresEnabledStatus[featureName]) {
FeaturesService.featuresEnabledStatus[featureName] = false;
} else {
FeaturesService.featuresEnabledStatus[featureName] = !FeaturesService.featuresEnabledStatus[featureName];
}
};
FeaturesService.setFeatureEnabledData = function(data) {
if (undefined === data.base) {
data.base = true;
}
FeaturesService.featuresEnabledStatus = data;
};
FeaturesService.setBaseRequiredForAllFeatures = function(reqdForAll) {
FeaturesService.baseRequiredForAllFeatures = reqdForAll;
};
FeaturesService.setRemoteSource = function(sourceURI, repeatIn, repeatCount) {
FeaturesService.remoteSource = sourceURI;
FeaturesService.checkRemoteSource();
if (FeaturesService.intervalId) {
$interval.cancel(FeaturesService.intervalId);
}
if (repeatIn) {
FeaturesService.intervalId = $interval(FeaturesService.checkRemoteSource, repeatIn, repeatCount);
if (repeatIn < 60000) {
$log.warn('setRemoteSource: Checking source at extremely short intervals is not advised.');
}
}
};
FeaturesService.checkRemoteSource = function() {
if (!FeaturesService.remoteSource) {
$log.warn('checkRemoteSource: No remote source has been set, returning');
return;
}
var promise = $http.get(FeaturesService.remoteSource);
var self = FeaturesService;
promise.then(function(response) {
self.setFeatureEnabledData(response.data);
return response.data;
})
.catch(function(errorResponse) {
var errorData = {
status: errorResponse.status,
method: errorResponse.config.method,
url: errorResponse.config.url,
statusText: errorResponse.statusText,
data: errorResponse.data
};
$log.error('checkRemoteSource: Errors encountered retrieving feature status information: ', errorData);
});
return promise;
};
FeaturesService.setHardCodedSource = function(data) {
FeaturesService.setFeatureEnabledData(data);
if (FeaturesService.intervalId) {
$interval.cancel(FeaturesService.intervalId);
FeaturesService.intervalId = null;
}
};
return FeaturesService;
}]);
})();
|